package com.yeziji.devops.sql;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.yeziji.common.CommonPage;
import com.yeziji.common.CommonSymbol;
import com.yeziji.devops.annotation.AccessProhibited;
import com.yeziji.devops.common.expcetion.JdbcException;
import com.yeziji.devops.common.msg.JdbcErrorMsg;
import com.yeziji.devops.config.ConfigProperties;
import com.yeziji.devops.constant.SqlExecuteTypeEnum;
import com.yeziji.devops.constant.mysql.MysqlFixedStatementEnum;
import com.yeziji.devops.constant.mysql.MysqlKeywordEnum;
import com.yeziji.devops.sql.base.SqlConstructorBase;
import com.yeziji.devops.sql.constructor.*;
import com.yeziji.devops.sql.info.DatabaseInfo;
import com.yeziji.devops.sql.model.mysql.SqlCaseInsensitive;
import com.yeziji.devops.sql.model.mysql.SqlColumn;
import com.yeziji.devops.sql.model.mysql.SqlKeyColumnUsage;
import com.yeziji.devops.utils.ParseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.sql.DataSource;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Devops Jdbc
 * <p>
 * 必须有数据源
 * </p>
 *
 * @author hwy
 * @since 2024/08/01 17:23
 **/
@Slf4j
@Component
public class DevopsJdbc extends JdbcTemplate {
    /**
     * 批量插入自动分批的阈值
     */
    public static int AUTO_PAGE = 2000;
    private final DataSource dataSource;
    private final ConfigProperties configProperties;

    public DevopsJdbc(DataSource dataSource, ConfigProperties configProperties) {
        this.configProperties = configProperties;
        // 选择数据源，优先选择 master 数据源
        DataSource masterDataSource = configProperties.getMasterDataSource();
        if (masterDataSource != null) {
            this.dataSource = masterDataSource;
        } else {
            log.warn("建议单独配置 devops 数据库/源");
            this.dataSource = dataSource;
        }
        super.setDataSource(this.dataSource);
    }

    // --- DDL

    /**
     * 备份数据表 DDL
     *
     * @param table 数据表
     * @return ddl 数据表
     */
    public String tableDDL(String table) {
        StringJoiner backupSql = new StringJoiner("\n");
        backupSql.add("-- ----------------------------");
        backupSql.add("-- " + table + " 表结构");
        backupSql.add("-- ----------------------------");
        Map<String, Object> query = this.query(SelectSqlConstructor.builder().sql("show create table " + table).build());
        Object createTable = query.get("Create Table");
        if (createTable != null) {
            backupSql.add("DROP TABLE IF EXISTS `" + table + "`;");
            backupSql.add(createTable + ";");
        }
        int total = this.count(table);
        backupSql.add("-- ----------------------------");
        backupSql.add("-- 记录" + table + "数据：" + total + " 行");
        backupSql.add("-- ----------------------------");
        if (total < AUTO_PAGE) {
            // 直接查询
            List<Map<String, Object>> maps = this.queryForList(SelectSqlConstructor.builder().table(table).build());
            this.appendDDL(table, backupSql, maps);
        } else {
            int ceil = (int) Math.ceil((double) total / AUTO_PAGE);
            for (int i = 0; i < ceil; i++) {
                SqlPageResult sqlPageResult = this.queryPage(PageSqlConstructor.builder().table(table).page(i).limit(AUTO_PAGE).build(), false);
                if (sqlPageResult.getData() instanceof Collection) {
                    List<Map<String, Object>> data = (List<Map<String, Object>>) sqlPageResult.getData();
                    this.appendDDL(table, backupSql, data);
                }
            }
        }
        log.info("获取导出数据表的 ddl -> {}", backupSql);
        return backupSql.toString();
    }

    /**
     * 备份数据库 DDL
     *
     * @param database 数据库
     * @return ddl 数据库 ddl
     */
    public String databaseDDL(String database) {
        StringJoiner databaseDDL = new StringJoiner("\n");
        databaseDDL.add("-- ----------------------------");
        databaseDDL.add("-- " + database + " 数据库结构");
        try (Connection connection = dataSource.getConnection()) {
            String path = ParseUtils.findDbByMysqlConnection(connection);
            List<Map<String, Object>> tables = new ArrayList<>();
            if (StrUtil.isNotBlank(path) && path.replace("/", "").trim().equals(database)) {
                // 存在 path 且于指定的 database 相同
                tables = this.queryForList(
                        SelectSqlConstructor.builder()
                                .sql(MysqlFixedStatementEnum.SHOW_TABLES.getValue())
                                .build()
                );
            } else {
                // 没有指定数据库，那么先获取 database
                List<Map<String, Object>> databases = this.queryForList(
                        SelectSqlConstructor.builder()
                                .sql(MysqlFixedStatementEnum.SHOW_DATABASES.getValue())
                                .build()
                );
                for (Map<String, Object> map : databases) {
                    for (Object value : map.values()) {
                        if (String.valueOf(value).equals(database)) {
                            // 找到指定的数据库
                            super.execute(MysqlKeywordEnum.USE.append(database).toStr());
                            tables = this.queryForList(
                                    SelectSqlConstructor.builder()
                                            .sql(MysqlFixedStatementEnum.SHOW_TABLES.getValue())
                                            .build()
                            );
                            break;
                        }
                    }
                }
            }

            if (CollectionUtil.isNotEmpty(tables)) {
                databaseDDL.add(
                        "-- 共有 "
                                + tables.size()
                                + " 张数据表，构建时间为："
                                + DateUtil.now()
                );
                databaseDDL.add("-- ----------------------------");
                for (Map<String, Object> table : tables) {
                    table.values().stream()
                            .findFirst()
                            .ifPresent(o -> databaseDDL.add(tableDDL(String.valueOf(o))));
                }
            } else {
                databaseDDL.add("-- 不存在数据表");
                databaseDDL.add("-- ----------------------------");
            }
            return databaseDDL.toString();
        } catch (SQLException e) {
            throw new JdbcException(JdbcErrorMsg.BUILD_DDL_FAILED, e);
        }
    }

    // --- jdbc basic query

    /**
     * 获取 count
     *
     * @param table 查询的表
     * @return {@link Integer} 表数据数量
     */
    public int count(String table) {
        String sql = "SELECT count(1) AS count FROM " + table;
        Integer total = super.queryForObject(sql, Integer.class);
        if (total == null) {
            total = 0;
        }
        this.countLogger(sql, total);
        return total;
    }

    /**
     * 普通查询
     *
     * @param sqlConstructor 查询 SQL 构造器
     * @return {@link Map} 查询结果集 -> key: column, value: column-value
     */
    public Map<String, Object> query(SelectSqlConstructor sqlConstructor) {
        String sql = sqlConstructor.buildSql();
        Map<String, Object> map = super.queryForMap(sql);
        this.searchLogger(sql, map);
        return map;
    }

    /**
     * 分页查询(自带 count)
     *
     * @param pageSqlConstructor 分页查询 sql 构造器
     * @return 分页结果
     */
    @AccessProhibited
    public SqlPageResult queryPage(PageSqlConstructor pageSqlConstructor) {
        return this.queryPage(pageSqlConstructor, true);
    }

    /**
     * 分页查询基本实现
     *
     * @param pageSqlConstructor 分页 sql 构造器
     * @param queryCount         是否查询条目
     * @return {@link CommonPage} 分页数据
     */
    @AccessProhibited
    public SqlPageResult queryPage(PageSqlConstructor pageSqlConstructor, boolean queryCount) {
        int total = 0;
        if (queryCount) {
            total = this.count(pageSqlConstructor.getTable());
            return SqlPageResult.buildByPageSqlConstructor(
                    pageSqlConstructor,
                    SqlResult.builder().result(this.queryForList(pageSqlConstructor)).build(),
                    total);
        }
        // 不需要分页
        SqlResult sqlResult = SqlResult.builder().result(this.queryForList(pageSqlConstructor)).build();
        if (sqlResult.getResult() instanceof Collection) {
            total = ((Collection<Object>) sqlResult.getResult()).size();
        }
        return SqlPageResult.buildByPageSqlConstructor(pageSqlConstructor, sqlResult, total);
    }

    /**
     * 查询 list
     *
     * @param sqlConstructor 查询 sql 构造器
     * @return 多行结果
     */
    public List<Map<String, Object>> queryForList(SelectSqlConstructor sqlConstructor) {
        String sql = sqlConstructor.buildSql();
        List<Map<String, Object>> list = super.queryForList(sql);
        searchLogger(sql, list);
        return list;
    }

    /**
     * 单条插入
     *
     * @param insertSqlConstructor 插入 sql 构造器
     * @return 单条插入的自增 id
     */
    @AccessProhibited
    public int insert(InsertSqlConstructor insertSqlConstructor) {
        if (StrUtil.isNotBlank(insertSqlConstructor.getGenDate())) {
            configProperties.insertFill(insertSqlConstructor);
        }
        String sql = insertSqlConstructor.buildSql();
        KeyHolder keyHolder = new GeneratedKeyHolder();
        List<List<Object>> values = insertSqlConstructor.getValues();
        if (values.size() == 1) {
            if (insertSqlConstructor.getAutoAdd() == null) {
                // 没有说是否为自增，那么就去数据库查找
                List<String> autoAddPrimaryKey = getAutoAddPrimaryKey(insertSqlConstructor.getTable());
                // 如果不为空就是存在自增
                insertSqlConstructor.setAutoAdd(!autoAddPrimaryKey.isEmpty());
            }
            // 单个插入
            int insertUpdate = super.update(
                    connection -> {
                        // 返回自增 id
                        PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                        List<Object> objects = values.get(0);
                        assert objects != null;
                        int i = 1;
                        for (Object obj : objects) {
                            ps.setObject(i++, obj);
                        }
                        return ps;
                    },
                    keyHolder);
            // 如果是自增，那么返回 id 否则生成影响行数
            int insert = insertSqlConstructor.getAutoAdd() ? Objects.requireNonNull(keyHolder.getKey()).intValue() : insertUpdate;
            insertLogger(sql, insertSqlConstructor, insert);
            return insert;
        }
        insertLogger(sql, insertSqlConstructor, 0);
        return -1;
    }

    /**
     * 更新数据
     *
     * @param updateSqlConstructor 更新 sql 构造器
     * @return 影响数据行数
     */
    @AccessProhibited
    public int update(UpdateSqlConstructor updateSqlConstructor) {
        if (StrUtil.isNotBlank(updateSqlConstructor.getGenDate())) {
            configProperties.updateFill(updateSqlConstructor);
        }
        String sql = updateSqlConstructor.buildSql();
        try (Connection connection = dataSource.getConnection()) {
            PreparedStatement ps = connection.prepareStatement(sql);
            Map<String, Object> data = updateSqlConstructor.getData();
            int i = 1;
            for (Map.Entry<String, Object> entry : data.entrySet()) {
                ps.setObject(i++, entry.getValue());
            }
            int update = ps.executeUpdate();
            this.updateLogger(sql, updateSqlConstructor, update);
            return update;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 删除数据
     *
     * @param deleteSqlConstructor 删除 sql 构造器
     * @return 影响行数
     */
    public int delete(DeleteSqlConstructor deleteSqlConstructor) {
        String sql = deleteSqlConstructor.buildSql();
        int delete = super.update(sql);
        this.deleteLogger(sql, delete);
        return delete;
    }

    /**
     * 创建语句
     *
     * @param createSqlConstructor 创建 SQL 构造器
     * @return {@link Integer} 影响行数
     */
    @AccessProhibited
    public int create(CreateSqlConstructor createSqlConstructor) {
        String sql = createSqlConstructor.buildSql();
        log.info(DateUtil.now() + " ==> 执行创建语句");
        log.info("==> 执行 SQL： {}", sql);
        return super.update(sql);
    }

    /**
     * 执行修改语句
     *
     * <p><b>该方法只适用于构造器自主构建的 sql 语句
     *
     * @param alterSqlConstructor 修改 sql 构造器
     * @return 影响行数
     */
    @AccessProhibited
    public int alter(AlterSqlConstructor alterSqlConstructor) {
        return this.alter(alterSqlConstructor.getSql());
    }

    /**
     * 更改 SQL
     * <p>执行任意 update 语句</p>
     *
     * @param alterSql 更改 SQL
     * @return {@link Integer} 影响行数
     */
    @Transactional(rollbackFor = {Exception.class})
    public int alter(String alterSql) {
        log.info(DateUtil.now() + " ==> 执行修改语句");
        log.info("==> 执行 SQL： {}", alterSql);
        return super.update(alterSql);
    }

    /**
     * SQL 基本执行器
     * <p>会根据 SQL 语句执行结果</p>
     *
     * @param sqlConstructor SQL 构造器
     * @return {@link Object} 执行结果
     */
    @AccessProhibited
    public Object execute(SqlConstructorBase sqlConstructor) {
        String sql = sqlConstructor.getSql();
        String prefix = sql.trim().substring(0, sql.indexOf(" "));
        SqlExecuteTypeEnum typeEnum = SqlExecuteTypeEnum.getByValue(prefix.toUpperCase());
        log.info("{} 执行自定义 sql -> {}", DateUtil.now(), sql);
        assert typeEnum != null;
        try {
            if (!typeEnum.equals(SqlExecuteTypeEnum.SELECT)) {
                return super.update(sql);
            } else {
                return super.queryForList(sql);
            }
        } catch (Exception e) {
            throw new JdbcException(JdbcErrorMsg.EXECUTE_FAILED, e);
        }
    }

    // --- 辅助方法

    /**
     * 获取自增的主键 - 需要选定数据源，如果没选定数据源则通过 TABLE_SCHEMA 来判断
     *
     * @param table 数据表名
     * @return 主键列表
     */
    public List<String> getAutoAddPrimaryKey(String table) {
        String sql =
                "SELECT COLUMN_NAME FROM information_schema.columns WHERE TABLE_NAME = '"
                        + table
                        + "' AND COLUMN_KEY = 'PRI' and EXTRA = 'auto_increment';";
        return this.queryForList(
                        SelectSqlConstructor.builder()
                                .sql(sql)
                                .build()).stream()
                .map(map -> String.valueOf(map.get("COLUMN_NAME")))
                .collect(Collectors.toList());
    }

    /**
     * 获取 table 所有字段
     *
     * @param table 数据表
     * @return 返回字段
     */
    public List<SqlColumn> getColumns(String table) {
        String sql = MysqlFixedStatementEnum.getFullFieldsFromTable(table);
        List<Map<String, Object>> maps = this.queryForList(SelectSqlConstructor.builder().sql(sql).build());
        List<SqlColumn> list = new ArrayList<>();
        if (maps != null) {
            for (Map<String, Object> map : maps) {
                list.add(SqlCaseInsensitive.convertBy(map).convertToSqlColumn());
            }
        }
        return list;
    }

    /**
     * 根据指定 sql 获取其键使用信息
     *
     * @param sql 查询 sql
     * @return {@link List} 键信息
     */
    public List<SqlKeyColumnUsage> getKeyColumnUsage(String sql) {
        List<SqlKeyColumnUsage> list = new ArrayList<>();
        List<Map<String, Object>> maps = this.queryForList(SelectSqlConstructor.builder().sql(sql).build());
        if (maps != null) {
            for (Map<String, Object> map : maps) {
                list.add(BeanUtil.mapToBean(map, SqlKeyColumnUsage.class, false, CopyOptions.create().ignoreNullValue()));
            }
        }
        return list;
    }

    /**
     * 根据 constraint 名称获取其键使用信息
     *
     * @param constraintName 约束名称
     * @return {@link List} 键信息
     */
    public List<SqlKeyColumnUsage> getKeyColumnUsageByConstraintName(String constraintName) {
        String sql = MysqlFixedStatementEnum.getKeyColumnUsageByConstraintName(constraintName);
        return this.getKeyColumnUsage(sql);
    }

    /**
     * 根据 table 获取其键使用信息
     *
     * @param table 数据表
     * @return {@link List} 键信息
     */
    public List<SqlKeyColumnUsage> getKeyColumnUsageByTable(String table) {
        try {
            String database = dataSource.getConnection().getMetaData().getDatabaseProductName();
            String sql = MysqlFixedStatementEnum.getKeyColumnUsageByDbTable(database, table);
            return this.getKeyColumnUsage(sql);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取表的字段类型
     *
     * @param table      数据表
     * @param columnName 字段名称
     * @return {@link String} - 字段类型
     */
    public SqlColumn getTableColumnInfo(String table, String columnName) {
        List<SqlColumn> columns = this.getColumns(table);
        if (CollectionUtil.isNotEmpty(columns)) {
            for (SqlColumn column : columns) {
                // 筛选出相同的名称
                if (column.getName().equals(columnName)) {
                    return column;
                }
            }
        }
        return null;
    }

    /**
     * 获取全部数据库
     *
     * @return {@link List} 数据库信息
     */
    public List<DatabaseInfo> getDatabases() {
        List<Map<String, Object>> databases = this.queryForList(MysqlFixedStatementEnum.SHOW_DATABASES.getValue());
        List<DatabaseInfo> list = new ArrayList<>();
        for (Map<String, Object> database : databases) {
            for (Map.Entry<String, Object> entry : database.entrySet()) {
                Object value = entry.getValue();
                super.execute(MysqlKeywordEnum.USE.append(value.toString()).toStr());
                List<Map<String, Object>> tables = this.queryForList(SelectSqlConstructor.builder().sql(MysqlFixedStatementEnum.SHOW_TABLES.getValue()).build());
                List<String> tableList = new ArrayList<>();
                for (Map<String, Object> table : tables) {
                    for (Map.Entry<String, Object> tableEntry : table.entrySet()) {
                        tableList.add(String.valueOf(tableEntry.getValue()));
                    }
                }
                list.add(new DatabaseInfo(String.valueOf(value), tableList));
            }
        }
        this.searchLogger(MysqlFixedStatementEnum.SHOW_DATABASES.getValue(), databases);
        return list;
    }

    /**
     * 获取全部数据表
     *
     * @param database 指定数据库
     * @return {@link List} 數據庫表信息
     */
    public List<String> getTables(String database) {
        super.execute(MysqlKeywordEnum.USE.append(database).toStr());
        List<Map<String, Object>> tables = this.queryForList(MysqlFixedStatementEnum.SHOW_TABLES.getValue());
        List<String> tableList = new ArrayList<>();
        for (Map<String, Object> table : tables) {
            for (Map.Entry<String, Object> tableEntry : table.entrySet()) {
                tableList.add(String.valueOf(tableEntry.getValue()));
            }
        }
        this.searchLogger(MysqlFixedStatementEnum.SHOW_DATABASES.getValue(), tables);
        return tableList;
    }

    /**
     * 追加 ddl
     *
     * @param table 数据表
     * @param sj    追加 sj
     * @param maps  数据
     */
    private void appendDDL(String table, StringJoiner sj, List<Map<String, Object>> maps) {
        for (Map<String, Object> map : maps) {
            StringJoiner insertDDL = new StringJoiner(" ");
            insertDDL.add(SqlExecuteTypeEnum.INSERT.getValue())
                    .add(MysqlKeywordEnum.INTO.getValue())
                    .add("`" + table + "`")
                    .add(MysqlKeywordEnum.VALUES.getValue());
            insertDDL.add(this.buildValuesInsertDDL(map.values()));
            sj.add(insertDDL + ";");
        }
    }

    /**
     * 构建 insert values ddl
     *
     * @param values 数据
     * @return ddl 字符串
     */
    private String buildValuesInsertDDL(Collection<Object> values) {
        StringJoiner sj = new StringJoiner(", ", "(", ")");
        for (Object value : values) {
            if (value == null) {
                sj.add("NULL");
            } else {
                String className = value.getClass().getName();
                if (className.equals(String.class.getName())
                        || className.equals(Timestamp.class.getName())
                        || className.equals(LocalDateTime.class.getName())
                        || className.equals(Date.class.getName())
                        || className.equals(java.sql.Date.class.getName())) {
                    if (JSONUtil.isTypeJSON(String.valueOf(value))) {
                        sj.add(JSONObject.toJSONString(value));
                    } else {
                        sj.add("\"" + value + "\"");
                    }
                } else if (className.equals(Boolean.class.getName())) {
                    if ((Boolean) value) {
                        sj.add("1");
                    } else {
                        sj.add("0");
                    }
                }
                // 字节符
                else if ("[B".equals(className)) {
                    byte[] bytes = (byte[]) value;
                    if (bytes.length == 0) {
                        sj.add("\"\"");
                    } else {
                        StringBuilder hex = new StringBuilder();
                        for (byte b : bytes) {
                            hex.append(String.format("%02X", b));
                        }
                        sj.add("0x" + hex);
                    }
                } else {
                    sj.add(String.valueOf(value));
                }
            }
        }
        return sj.toString();
    }

    // --- logger 日志方法

    /**
     * 查询日志
     *
     * @param sql    执行 sql
     * @param result 查询结果
     */
    private void searchLogger(String sql, List<Map<String, Object>> result) {
        if (configProperties.isLog()) {
            StringJoiner sj = new StringJoiner("\n");
            sj.add(DateUtil.now() + " ==> 执行查询语句");
            sj.add("==> 查询 SQL：" + sql);
            if (!CollectionUtils.isEmpty(result)) {
                // 构建 columns
                StringJoiner columnSj = new StringJoiner(", ");
                Map<String, Object> first = result.get(0);
                for (Map.Entry<String, Object> entry : first.entrySet()) {
                    columnSj.add(entry.getKey());
                }
                sj.add("<== 查询字段：" + columnSj);
                // 构建 result
                int i = 1;
                for (Map<String, Object> map : result) {
                    StringJoiner resultSj = new StringJoiner(", ");
                    for (Map.Entry<String, Object> entry : map.entrySet()) {
                        String valStr = String.valueOf(entry.getValue());
                        if (valStr.length() > 1024) {
                            resultSj.add("<<长文本>>");
                        } else {
                            resultSj.add(valStr);
                        }
                    }
                    sj.add("<== 结果行" + i++ + "：" + resultSj);
                }
                sj.add("<== 数据总数：" + result.size());
            } else {
                sj.add("查询结果为空");
            }
            log.info(sj.toString());
        }
    }

    /**
     * 查询日志
     *
     * @param sql    查询的 sql
     * @param result 查询结果
     */
    private void searchLogger(String sql, Map<String, Object> result) {
        this.searchLogger(sql, List.of(result));
    }

    /**
     * 插入日志
     *
     * @param sql                  执行 sql
     * @param insertSqlConstructor 插入 sql 构造对象
     * @param ids                  一般是自增主键
     */
    private void insertLogger(String sql, InsertSqlConstructor insertSqlConstructor, Integer... ids) {
        if (configProperties.isLog()) {
            StringJoiner sj = new StringJoiner("\n");
            if (ids.length == 0) {
                sj.add(DateUtil.now() + " ==> 执行新增失败 sql ==>" + sql);
                log.info(sj.toString());
                return;
            } else if (insertSqlConstructor.getValues().size() > 1) {
                sj.add(DateUtil.now() + " ==> 执行批量新增语句");
            } else {
                sj.add(DateUtil.now() + " ==> 执行新增语句");
            }
            sj.add("==> 新增 SQL：" + sql);
            List<List<Object>> values = insertSqlConstructor.getValues();
            if (!CollectionUtils.isEmpty(values)) {
                // 打印参数
                for (List<Object> value : values) {
                    StringJoiner valueSj = new StringJoiner(", ");
                    for (Object obj : value) {
                        valueSj.add(obj + "(" + obj.getClass().getName() + ")");
                    }
                    sj.add("==> 传入参数：" + valueSj);
                }
            }
            if (insertSqlConstructor.getAutoAdd()) {
                // 插入成功
                sj.add("<== 影响行数：" + ids.length);
                sj.add("<== 新增id主键：" + StrUtil.join(CommonSymbol.COMMA, ids));
            } else {
                sj.add("<== 影响行数：" + ids[0]);
            }
            log.info(sj.toString());
        }
    }

    /**
     * 更新日志
     *
     * @param sql      更新的 sql
     * @param affected 影响行数
     */
    private void updateLogger(String sql, UpdateSqlConstructor updateSqlConstructor, int affected) {
        if (configProperties.isLog()) {
            StringJoiner sj = new StringJoiner("\n");
            if (affected == 0) {
                sj.add(DateUtil.now() + " ==> 执行更新失败 sql ==>" + sql);
                log.info(sj.toString());
                return;
            } else {
                sj.add(DateUtil.now() + " ==> 执行更新语句");
            }
            sj.add("==> 更新 SQL：" + sql);
            StringJoiner valueSj = new StringJoiner(", ");
            for (Map.Entry<String, Object> entry : updateSqlConstructor.getData().entrySet()) {
                Object obj = entry.getValue();
                valueSj.add(obj + "(" + obj.getClass().getName() + ")");
            }
            sj.add("==> 传入参数：" + valueSj);
            sj.add("<== 影响行数：" + affected);
            log.info(sj.toString());
        }
    }

    /**
     * 删除日志
     *
     * @param sql      删除的 sql
     * @param affected 影响行数
     */
    private void deleteLogger(String sql, int affected) {
        if (configProperties.isLog()) {
            StringJoiner sj = new StringJoiner("\n");
            if (affected == 0) {
                sj.add(DateUtil.now() + " ==> 执行删除失败 sql ==>" + sql);
                log.info(sj.toString());
                return;
            } else {
                sj.add(DateUtil.now() + " ==> 执行删除语句");
            }
            sj.add("==> 删除 SQL：" + sql);
            sj.add("<== 影响行数：" + affected);
            log.info(sj.toString());
        }
    }

    /**
     * count 日志
     *
     * @param sql   执行 sql
     * @param total count 数量
     */
    private void countLogger(String sql, int total) {
        if (configProperties.isLog()) {
            StringJoiner sj = new StringJoiner("\n");
            sj.add(DateUtil.now() + " ==> 执行查询 count");
            sj.add("==> 查询 count SQL：" + sql).add("<== count 数：" + total);
            log.info(sj.toString());
        }
    }
}
